/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package selector

import (
	"fmt"

	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/protos"
	"hundsun.com/hsl/hschain/protos/consensus/dpos"
)

// ElectionContext 记录selector处投票交易的进度，每一轮第一块记录，做两件事情：
// 1）统计上一轮投票结果
// 2）统计截止当前轮第一块，尚未失效的候选人和委托关系
type ElectionContext struct {
	// 已处理到哪一轮
	Round int64 `json:"round"`

	// Round中第一块的块号
	FirstBlockOfRound uint64 `json:"first_block_of_round"`

	// 未失效的候选节点，key是候选节点帐号地址
	Candidates map[protos.Address]*Candidate `json:"candidates"`

	// 未失效的委托关系，key是投票节点帐号地址
	Delegations map[protos.Address]*Delegation `json:"delegations"`

	Log logging.Log

	// 处理到了哪一块
	LastHandledBlock uint64
}

// CountBallotsAndSweep 统计选票并清理过期的Candidate和Delegation
func (e *ElectionContext) CountBallotsAndSweep(voteRound int64) map[protos.Address]uint64 {
	// 在当前round中生效的候选人地址及权重
	result := make(map[protos.Address]uint64)
	i := 0
	countRoundLog := e.Log.WithField("rd", voteRound+1)
	effectiveRound := voteRound + 2
	for k, v := range e.Candidates {
		//　清除过期的候选人
		if v.EndRound < effectiveRound {
			delete(e.Candidates, k)
		}
		if v.StartRound <= effectiveRound && v.EndRound > effectiveRound {
			result[k] = v.Weight
			countRoundLog.Debugf("candidates[%d] '%s' in pre voteRound,weight:%d", i, k, v.Weight)
			i++
		}
	}
	for k, v := range e.Delegations {
		if v.EndRound < effectiveRound {
			delete(e.Delegations, k)
		}
		if v.StartRound <= effectiveRound && v.EndRound > effectiveRound {
			_, ok := result[v.AgentAddress]
			if !ok {
				countRoundLog.Warnf("invalid delegation '%s' in pre voteRound: agent is not a candidate", v)
			} else {
				result[v.AgentAddress] += v.Weight
				countRoundLog.Debugf("delegation[client=%s,agent=%s] in pre voteRound,weight:%d", k, v.AgentAddress, v.Weight)
			}

		}
	}
	return result
}

// AddBallot 往上下文中添加一个投票
func (e *ElectionContext) AddBallot(ballot *WeightedBallot, blockNumberOfBallot uint64) {
	switch ballot.Type {
	case dpos.Ballot_TypeNomination:
		candidate := ballot.GetNomination()
		if candidate == nil {
			e.Log.Warn("nil Candidate in Ballot_NominateCandidate")
			return
		}
		e.nominateCandidate(blockNumberOfBallot, ballot.VoterAddress, candidate, ballot.Weight)
	case dpos.Ballot_TypeRevokeNomination:
		e.revokeCandidate(blockNumberOfBallot, ballot.VoterAddress)
	case dpos.Ballot_TypeDelegation:
		delegation := ballot.GetDelegation()
		if delegation == nil {
			e.Log.Warn("nil Candidate in Ballot_Delegate")
			return
		}
		e.delegate(blockNumberOfBallot, ballot.VoterAddress, delegation, ballot.Weight)
	case dpos.Ballot_TypeRevokeDelegation:
		e.revokeDelegate(blockNumberOfBallot, ballot.VoterAddress)
	}
}

func (e *ElectionContext) nominateCandidate(blkNumber uint64, voter protos.Address, ballotContent *dpos.NominationBallotContent, weight uint64) {
	if ballotContent.StartRound >= ballotContent.EndRound {
		e.Log.Warnf("invalid NominationBallotContent[voter=%s,startRound=%d,endRound=%d] in block '%d': startRound>=endRound",
			voter, ballotContent.StartRound, ballotContent.EndRound, blkNumber)
		return
	}

	if ballotContent.EndRound < e.Round {
		e.Log.Warnf("invalid NominationBallotContent[voter=%s,startRound=%d,endRound=%d] in block '%d': endRound is less than current round '%d'",
			voter, ballotContent.StartRound, ballotContent.EndRound, blkNumber, e.Round)
		return
	}
	candidate := &Candidate{
		Address:    voter,
		PeerID:     ballotContent.PeerID,
		StartRound: ballotContent.StartRound,
		EndRound:   ballotContent.EndRound,
		Weight:     weight,
	}

	if prevCandidate, ok := e.Candidates[voter]; ok {
		e.Log.Debugf("previous %s replaced with %s in block %d", prevCandidate, candidate, blkNumber)
	} else {
		e.Log.Debugf("new '%s' added", candidate)
	}
	e.Candidates[voter] = candidate

}

func (e *ElectionContext) revokeCandidate(blkNumber uint64, voter protos.Address) {
	if prevCandidate, ok := e.Candidates[voter]; ok {
		delete(e.Candidates, voter)
		e.Log.Debugf("revoked %s in block %d", prevCandidate, blkNumber)
	} else {
		e.Log.Warnf("voter '%s' is not a Candidate and doesn't need to be revoked", voter)
	}
}

func (e *ElectionContext) delegate(blkNumber uint64, voter protos.Address, ballotContent *dpos.DelegationBallotContent, weight uint64) {
	if ballotContent.StartRound >= ballotContent.EndRound {
		e.Log.Warnf("invalid DelegationBallotContent[voter=%s,Candidate=%s,startRound=%d,endRound=%d] in block '%d': startRound>=endRound",
			voter, ballotContent.AgentAddress, ballotContent.StartRound, ballotContent.EndRound, blkNumber)
		return
	}

	if ballotContent.EndRound < e.Round {
		e.Log.Warnf("invalid DelegationBallotContent[voter=%s,Candidate=%s,startRound=%d,endRound=%d] in block '%d': endRound is less than current round '%d'",
			voter, ballotContent.AgentAddress, ballotContent.StartRound, ballotContent.EndRound, blkNumber, e.Round)
		return
	}

	if string(voter) == ballotContent.AgentAddress {
		e.Log.Warnf("invalid DelegationBallotContent[voter=%s,Candidate=%s,startRound=%d,endRound=%d] in block '%d': cannot delegate to oneself",
			voter, ballotContent.AgentAddress, ballotContent.StartRound, ballotContent.EndRound, blkNumber)
		return
	}

	if _, ok := e.Candidates[protos.Address(ballotContent.AgentAddress)]; !ok {
		e.Log.Warnf("invalid DelegationBallotContent[voter=%s,Candidate=%s,startRound=%d,endRound=%d] in block '%d': invalid Candidate",
			voter, ballotContent.AgentAddress, ballotContent.StartRound, ballotContent.EndRound, blkNumber)
		return
	}
	delegation := &Delegation{
		ClientAddress: voter,
		AgentAddress:  protos.Address(ballotContent.AgentAddress),
		StartRound:    ballotContent.StartRound,
		EndRound:      ballotContent.EndRound,
		Weight:        weight,
	}

	if preDelegation, ok := e.Delegations[voter]; ok {
		e.Log.Debugf("previous %s replaced with %s in block %d", preDelegation, delegation, blkNumber)
	} else {
		e.Log.Debugf("new %s added", delegation)
	}
	e.Delegations[voter] = delegation
}

func (e *ElectionContext) revokeDelegate(blkNumber uint64, voter protos.Address) {
	if preDelegation, ok := e.Delegations[voter]; ok {
		delete(e.Candidates, voter)
		e.Log.Debugf("revoked %s in block %d", preDelegation, blkNumber)
	} else {
		e.Log.Warnf("voter '%s' is not a Candidate and doesn't need to be revoked", voter)
	}
}

// Clone 对此结构体进行一个深拷贝
func (e *ElectionContext) Clone() *ElectionContext {
	cloned := &ElectionContext{
		Round:             e.Round,
		FirstBlockOfRound: e.FirstBlockOfRound,
		Candidates:        make(map[protos.Address]*Candidate, len(e.Candidates)),
		Delegations:       make(map[protos.Address]*Delegation, len(e.Delegations)),
		Log:               e.Log,
		LastHandledBlock:  e.LastHandledBlock,
	}

	for k, v := range e.Candidates {
		cloned.Candidates[k] = v.clone()
	}

	for k, v := range e.Delegations {
		cloned.Delegations[k] = v.clone()
	}

	return cloned
}

// Candidate 能被选为miner的节点
type Candidate struct {
	// 节点帐号地址
	Address protos.Address `json:"address"`

	// 节点selector模块所在网络地址
	PeerID string `json:"peer_id"`

	//生效轮次（含）
	StartRound int64 `json:"start_round"`

	//失效轮次（不含）
	EndRound int64 `json:"end_round"`

	Weight uint64 `json:"weight"`
}

//clone 对此结构体进行一个深拷贝
func (c *Candidate) clone() *Candidate {
	cloned := &Candidate{
		Address:    c.Address,
		StartRound: c.StartRound,
		EndRound:   c.EndRound,
		Weight:     c.Weight,
	}
	if c.PeerID != "" {
		cloned.PeerID = c.PeerID
	}
	return cloned
}

func (c *Candidate) String() string {
	return fmt.Sprintf("Candidate[address=%s,peerID=%s,startRound=%d,endRound=%d,weight=%d]", c.Address, c.PeerID, c.StartRound, c.EndRound, c.Weight)
}

// Delegation 委托关系
type Delegation struct {
	//委托节点地址
	ClientAddress protos.Address `json:"address"`

	//代理节点地址
	AgentAddress protos.Address `json:"agent_address"`

	//生效轮次（含）
	StartRound int64 `json:"start_round"`

	//失效轮次（不含）
	EndRound int64 `json:"end_round"`

	Weight uint64 `json:"weight"`
}

//clone 对此结构体进行一个深拷贝
func (d *Delegation) clone() *Delegation {
	cloned := &Delegation{
		ClientAddress: d.ClientAddress,
		AgentAddress:  d.AgentAddress,
		StartRound:    d.StartRound,
		EndRound:      d.EndRound,
		Weight:        d.Weight,
	}
	return cloned
}

func (d *Delegation) String() string {
	return fmt.Sprintf("Delegation[voter=%s,Candidate=%s,startRound=%d,endRound=%d,weight=%d]", d.ClientAddress, d.AgentAddress, d.StartRound, d.EndRound, d.Weight)
}

// WeightedBallot 带有权重的选票
type WeightedBallot struct {
	VoterAddress protos.Address
	*dpos.Ballot
	Weight uint64
}
